﻿using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Reflection;

namespace ScriptEngine.Script.Util
{
	public static class JsScriptObjectTransformUtil
	{
		private static bool IsBaseType(Type type)
		{
			return type.IsPrimitive || type == typeof(string) || type == typeof(object);
		}

		public static object TransformTo(this ExpandoObject obj, Type type)
		{
			ConstructorInfo constructor = type.GetConstructor(new Type[0]);
			if (constructor == null)
			{
				throw new Exception($"待转换的对象;{type},必须存在一个空参构造函数");
			}

			IDictionary<string, object> srcObj = obj;
			object targetObject = constructor.Invoke(new object[0]);

			foreach (PropertyInfo propertyInfo in type.GetProperties())
			{
				ScriptObjectPropertyAttribute scriptObjectProperty = propertyInfo.GetCustomAttribute<ScriptObjectPropertyAttribute>();
				if (scriptObjectProperty == null)
				{
					continue;
				}

				object srcProperty = srcObj.ContainsKey(scriptObjectProperty.ObjectNameInScriptObject)
					? srcObj[scriptObjectProperty.ObjectNameInScriptObject]
					: null;
				if (scriptObjectProperty.Require && srcProperty == null)
				{
					throw new NullReferenceException($"未在脚本对象中找到必要属性:{scriptObjectProperty.ObjectNameInScriptObject}");
				}

				if (srcProperty == null && !scriptObjectProperty.Require)
				{
					propertyInfo.SetValue(targetObject, scriptObjectProperty.DefaultValue);
					continue;
				}

				if (IsBaseType(propertyInfo.PropertyType))
				{
					propertyInfo.SetValue(targetObject, Convert.ChangeType(srcProperty, propertyInfo.PropertyType));
				}
				else
				{
					if (propertyInfo.PropertyType.IsArray)
					{
						Type targetElementType = propertyInfo.PropertyType.GetElementType();
						if (targetElementType.IsGenericType)
						{
							throw new Exception($"不支持的数组类型: {targetElementType}");
						}

						if (srcProperty == null)
						{
							propertyInfo.SetValue(targetObject, null);
							continue;
						}

						if (srcProperty.GetType() != typeof(object[]))
						{
							throw new Exception($"目标对象:{propertyInfo.Name}的类型为数组类型，但是脚本对象非数组类型");
						}

						object[] srcPropArr = srcProperty as object[];

						Array targetArr = Array.CreateInstance(targetElementType, srcPropArr.Length);
						for (var i = 0; i < srcPropArr.Length; i++)
						{
							if (IsBaseType(targetElementType))
							{
								targetArr.SetValue(Convert.ChangeType(srcPropArr[i], targetElementType), i);
							}
							else if (srcPropArr[i] is ExpandoObject srcPropObj)
							{
								targetArr.SetValue(srcPropObj.TransformTo(targetElementType), i);
							}
							else
							{
								throw new Exception($"不支持的类型转换: [{srcPropArr[i].GetType()}] to [{targetElementType}]");
							}
						}

						propertyInfo.SetValue(targetObject, targetArr);
					}
					else if (srcProperty is ExpandoObject srcPropObj)
					{
						if (propertyInfo.PropertyType.IsAssignableFrom(typeof(ExpandoObject)))
						{
							propertyInfo.SetValue(targetObject, srcPropObj);
						}
						else
						{
							propertyInfo.SetValue(targetObject, srcPropObj.TransformTo(propertyInfo.PropertyType));
						}
					}
					else
					{
						throw new Exception($"不支持的转换类型: [{srcProperty.GetType()}] to [{propertyInfo.PropertyType}]");
					}
				}
			}

			return targetObject;
		}

		public static T TransformTo<T>(this ExpandoObject obj)
		{
			return (T)obj.TransformTo(typeof(T));
		}
	}
}
